using System;
using ch.ethz.ssh2.crypto;
using ch.ethz.ssh2.crypto.digest;
using ch.ethz.ssh2.signature;
using ch.ethz.ssh2.util;
using Org.BouncyCastle.Security;
namespace ch.ethz.ssh2
{

    /// <summary> The <code>KnownHosts</code> class is a handy tool to verify received server hostkeys
    /// based on the information in <code>known_hosts</code> files (the ones used by OpenSSH).
    /// <p>
    /// It offers basically an in-memory database for known_hosts entries, as well as some
    /// helper functions. Entries from a <code>known_hosts</code> file can be loaded at construction time.
    /// It is also possible to add more keys later (e.g., one can parse different
    /// <code>known_hosts<code> files).
    /// <p>
    /// It is a thread safe implementation, therefore, you need only to instantiate one
    /// <code>KnownHosts</code> for your whole application.
    /// 
    /// </summary>
    /// <author>  Christian Plattner
    /// </author>
    /// <version>  2.50, 03/15/10
    /// </version>

    public class KnownHosts
    {
        public const int HOSTKEY_IS_OK = 0;
        public const int HOSTKEY_IS_NEW = 1;
        public const int HOSTKEY_HAS_CHANGED = 2;

        //UPGRADE_NOTE: Field 'EnclosingInstance' was added to class 'KnownHostsEntry' to access its enclosing instance. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1019'"
        private class KnownHostsEntry
        {
            private void InitBlock(KnownHosts enclosingInstance)
            {
                this.enclosingInstance = enclosingInstance;
            }
            private KnownHosts enclosingInstance;
            public KnownHosts Enclosing_Instance
            {
                get
                {
                    return enclosingInstance;
                }

            }
            internal System.String[] patterns;
            internal System.Object key;

            internal KnownHostsEntry(KnownHosts enclosingInstance, System.String[] patterns, System.Object key)
            {
                InitBlock(enclosingInstance);
                this.patterns = patterns;
                this.key = key;
            }
        }

        //UPGRADE_TODO: Class 'java.util.LinkedList' was converted to 'System.Collections.ArrayList' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilLinkedList'"
        private System.Collections.ArrayList publicKeys = new System.Collections.ArrayList();

        public KnownHosts()
        {
        }

        public KnownHosts(char[] knownHostsData)
        {
            initialize(knownHostsData);
        }

        public KnownHosts(System.IO.FileInfo knownHosts)
        {
            initialize(knownHosts);
        }

        /// <summary> Adds a single public key entry to the database. Note: this will NOT add the public key
        /// to any physical file (e.g., "~/.ssh/known_hosts") - use <code>addHostkeyToFile()</code> for that purpose.
        /// This method is designed to be used in a {@link ServerHostKeyVerifier}.
        /// 
        /// </summary>
        /// <param name="hostnames">a list of hostname patterns - at least one most be specified. Check out the
        /// OpenSSH sshd man page for a description of the pattern matching algorithm.
        /// </param>
        /// <param name="serverHostKeyAlgorithm">as passed to the {@link ServerHostKeyVerifier}.
        /// </param>
        /// <param name="serverHostKey">as passed to the {@link ServerHostKeyVerifier}.
        /// </param>
        /// <throws>  IOException </throws>
        public virtual void addHostkey(System.String[] hostnames, System.String serverHostKeyAlgorithm, byte[] serverHostKey)
        {
            if (hostnames == null)
                throw new System.ArgumentException("hostnames may not be null");

            if ("ssh-rsa".Equals(serverHostKeyAlgorithm))
            {
                RSAPublicKey rpk = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey);

                //lock (publicKeys.SyncRoot)
                lock (publicKeys)
                {
                    publicKeys.Add(new KnownHostsEntry(this, hostnames, rpk));
                }
            }
            else if ("ssh-dss".Equals(serverHostKeyAlgorithm))
            {
                DSAPublicKey dpk = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey);

                //lock (publicKeys.SyncRoot)
                lock (publicKeys)
                {
                    publicKeys.Add(new KnownHostsEntry(this, hostnames, dpk));
                }
            }
            else
                throw new System.IO.IOException("Unknwon host key type (" + serverHostKeyAlgorithm + ")");
        }

        /// <summary> Parses the given known_hosts data and adds entries to the database.
        /// 
        /// </summary>
        /// <param name="knownHostsData">
        /// </param>
        /// <throws>  IOException </throws>
        public virtual void addHostkeys(char[] knownHostsData)
        {
            initialize(knownHostsData);
        }

        /// <summary> Parses the given known_hosts file and adds entries to the database.
        /// 
        /// </summary>
        /// <param name="knownHosts">
        /// </param>
        /// <throws>  IOException </throws>
        public virtual void addHostkeys(System.IO.FileInfo knownHosts)
        {
            initialize(knownHosts);
        }

        /// <summary> Generate the hashed representation of the given hostname. Useful for adding entries
        /// with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen).
        /// 
        /// </summary>
        /// <param name="hostname">
        /// </param>
        /// <returns> the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA="
        /// </returns>
        public static System.String createHashedHostname(System.String hostname)
        {
            SHA1 sha1 = new SHA1();

            byte[] salt = new byte[sha1.DigestLength];

            new SecureRandom().NextBytes(salt);

            byte[] hash = hmacSha1Hash(salt, hostname);

            System.String base64_salt = new System.String(Base64.encode(salt));
            System.String base64_hash = new System.String(Base64.encode(hash));

            return new System.Text.StringBuilder("|1|" + base64_salt + "|" + base64_hash).ToString();
        }

        private static byte[] hmacSha1Hash(byte[] salt, System.String hostname)
        {
            SHA1 sha1 = new SHA1();

            if (salt.Length != sha1.DigestLength)
                throw new System.ArgumentException("Salt has wrong length (" + salt.Length + ")");

            HMAC hmac = new HMAC(sha1, salt, salt.Length);

            hmac.update(StringEncoder.GetBytes(hostname));

            byte[] dig = new byte[hmac.DigestLength];

            hmac.digest(dig);

            return dig;
        }

        private bool checkHashed(System.String entry, System.String hostname)
        {
            if (entry.StartsWith("|1|") == false)
                return false;

            //UPGRADE_WARNING: Method 'java.lang.String.indexOf' was converted to 'System.String.IndexOf' which may throw an exception. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1101'"
            int delim_idx = entry.IndexOf('|', 3);

            if (delim_idx == -1)
                return false;

            System.String salt_base64 = entry.Substring(3, (delim_idx) - (3));
            System.String hash_base64 = entry.Substring(delim_idx + 1);

            byte[] salt = null;
            byte[] hash = null;

            try
            {
                salt = Base64.decode(salt_base64.ToCharArray());
                hash = Base64.decode(hash_base64.ToCharArray());
            }
            catch (System.IO.IOException e)
            {
                return false;
            }

            SHA1 sha1 = new SHA1();

            if (salt.Length != sha1.DigestLength)
                return false;

            byte[] dig = hmacSha1Hash(salt, hostname);

            for (int i = 0; i < dig.Length; i++)
                if (dig[i] != hash[i])
                    return false;

            return true;
        }

        private int checkKey(System.String remoteHostname, System.Object remoteKey)
        {
            int result = HOSTKEY_IS_NEW;

            //lock (publicKeys.SyncRoot)
            lock (publicKeys)
            {
                System.Collections.IEnumerator i = publicKeys.GetEnumerator();

                //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
                while (i.MoveNext())
                {
                    //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                    KnownHostsEntry ke = (KnownHostsEntry)i.Current;

                    if (hostnameMatches(ke.patterns, remoteHostname) == false)
                        continue;

                    bool res = matchKeys(ke.key, remoteKey);

                    if (res == true)
                        return HOSTKEY_IS_OK;

                    result = HOSTKEY_HAS_CHANGED;
                }
            }
            return result;
        }

        private System.Collections.ArrayList getAllKeys(System.String hostname)
        {
            System.Collections.ArrayList keys = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));

            //lock (publicKeys.SyncRoot)
            lock (publicKeys)
            {
                System.Collections.IEnumerator i = publicKeys.GetEnumerator();

                //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
                while (i.MoveNext())
                {
                    //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                    KnownHostsEntry ke = (KnownHostsEntry)i.Current;

                    if (hostnameMatches(ke.patterns, hostname) == false)
                        continue;

                    keys.Add(ke.key);
                }
            }

            return keys;
        }

        /// <summary> Try to find the preferred order of hostkey algorithms for the given hostname.
        /// Based on the type of hostkey that is present in the internal database
        /// (i.e., either <code>ssh-rsa</code> or <code>ssh-dss</code>)
        /// an ordered list of hostkey algorithms is returned which can be passed
        /// to <code>Connection.setServerHostKeyAlgorithms</code>. 
        /// 
        /// </summary>
        /// <param name="hostname">
        /// </param>
        /// <returns> <code>null</code> if no key for the given hostname is present or
        /// there are keys of multiple types present for the given hostname. Otherwise,
        /// an array with hostkey algorithms is returned (i.e., an array of length 2).
        /// </returns>
        public virtual System.String[] getPreferredServerHostkeyAlgorithmOrder(System.String hostname)
        {
            System.String[] algos = recommendHostkeyAlgorithms(hostname);

            if (algos != null)
                return algos;

            System.Net.IPAddress[] ipAdresses = null;

            try
            {
                //UPGRADE_TODO: The equivalent in .NET for method 'java.net.InetAddress.getAllByName' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                ipAdresses = System.Net.Dns.GetHostByName(hostname).AddressList;
            }
            catch (System.Exception e)
            {
                return null;
            }

            for (int i = 0; i < ipAdresses.Length; i++)
            {
                algos = recommendHostkeyAlgorithms(ipAdresses[i].ToString());

                if (algos != null)
                    return algos;
            }

            return null;
        }

        private bool hostnameMatches(System.String[] hostpatterns, System.String hostname)
        {
            bool isMatch = false;
            bool negate = false;

            hostname = hostname.ToLower();

            for (int k = 0; k < hostpatterns.Length; k++)
            {
                if (hostpatterns[k] == null)
                    continue;

                System.String pattern = null;

                /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed
                * entries in lines with multiple entries).
                */

                if ((hostpatterns[k].Length > 0) && (hostpatterns[k][0] == '!'))
                {
                    pattern = hostpatterns[k].Substring(1);
                    negate = true;
                }
                else
                {
                    pattern = hostpatterns[k];
                    negate = false;
                }

                /* Optimize, no need to check this entry */

                if ((isMatch) && (negate == false))
                    continue;

                /* Now compare */

                if (pattern[0] == '|')
                {
                    if (checkHashed(pattern, hostname))
                    {
                        if (negate)
                            return false;
                        isMatch = true;
                    }
                }
                else
                {
                    pattern = pattern.ToLower();

                    if ((pattern.IndexOf('?') != -1) || (pattern.IndexOf('*') != -1))
                    {
                        if (pseudoRegex(pattern.ToCharArray(), 0, hostname.ToCharArray(), 0))
                        {
                            if (negate)
                                return false;
                            isMatch = true;
                        }
                    }
                    else if (String.CompareOrdinal(pattern, hostname) == 0)
                    {
                        if (negate)
                            return false;
                        isMatch = true;
                    }
                }
            }

            return isMatch;
        }

        private void initialize(char[] knownHostsData)
        {
            //UPGRADE_ISSUE: Constructor 'java.io.BufferedReader.BufferedReader' was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1000_javaioBufferedReaderBufferedReader_javaioReader'"
            System.IO.StringReader br = new System.IO.StringReader(Convert.ToString(knownHostsData));

            while (true)
            {
                System.String line = br.ReadLine();

                if (line == null)
                    break;

                line = line.Trim();

                if (line.StartsWith("#"))
                    continue;

                System.String[] arr = line.Split(' ');

                if (arr.Length >= 3)
                {
                    if ((String.CompareOrdinal(arr[1], "ssh-rsa") == 0) || (String.CompareOrdinal(arr[1], "ssh-dss") == 0))
                    {
                        System.String[] hostnames = arr[0].Split(',');

                        byte[] msg = Base64.decode(arr[2].ToCharArray());

                        addHostkey(hostnames, arr[1], msg);
                    }
                }
            }
        }

        private void initialize(System.IO.FileInfo knownHosts)
        {
            char[] buff = new char[512];

            System.IO.StringWriter cw = new System.IO.StringWriter();

            CreateNewFile(knownHosts);

            //UPGRADE_TODO: Constructor 'java.io.FileReader.FileReader' was converted to 'System.IO.StreamReader' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073'"
            System.IO.StreamReader fr = new System.IO.StreamReader(knownHosts.FullName, System.Text.Encoding.Default);

            while (true)
            {
                //UPGRADE_TODO: Method 'java.io.Reader.read' was converted to 'System.IO.StreamReader.Read' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioReaderread_char[]'"
                int len = fr.Read((System.Char[])buff, 0, buff.Length);
                if (len < 0)
                    break;
                cw.Write(buff, 0, len);
            }

            fr.Close();

            initialize(cw.ToString().ToCharArray());
        }

        private bool CreateNewFile(System.IO.FileInfo path)
        {
            if (path.Exists)
            {
                return false;
            }
            else
            {
                System.IO.FileStream createdFile = path.Create();
                createdFile.Close();
                return true;
            }
        }

        private bool matchKeys(System.Object key1, System.Object key2)
        {
            if ((key1 is RSAPublicKey) && (key2 is RSAPublicKey))
            {
                RSAPublicKey savedRSAKey = (RSAPublicKey)key1;
                RSAPublicKey remoteRSAKey = (RSAPublicKey)key2;

                if (savedRSAKey.E.Equals(remoteRSAKey.E) == false)
                    return false;

                if (savedRSAKey.N.Equals(remoteRSAKey.N) == false)
                    return false;

                return true;
            }

            if ((key1 is DSAPublicKey) && (key2 is DSAPublicKey))
            {
                DSAPublicKey savedDSAKey = (DSAPublicKey)key1;
                DSAPublicKey remoteDSAKey = (DSAPublicKey)key2;

                if (savedDSAKey.G.Equals(remoteDSAKey.G) == false)
                    return false;

                if (savedDSAKey.P.Equals(remoteDSAKey.P) == false)
                    return false;

                if (savedDSAKey.Q.Equals(remoteDSAKey.Q) == false)
                    return false;

                if (savedDSAKey.Y.Equals(remoteDSAKey.Y) == false)
                    return false;

                return true;
            }

            return false;
        }

        private bool pseudoRegex(char[] pattern, int i, char[] match, int j)
        {
            /* This matching logic is equivalent to the one present in OpenSSH 4.1 */

            while (true)
            {
                /* Are we at the end of the pattern? */

                if (pattern.Length == i)
                    return (match.Length == j);

                if (pattern[i] == '*')
                {
                    i++;

                    if (pattern.Length == i)
                        return true;

                    if ((pattern[i] != '*') && (pattern[i] != '?'))
                    {
                        while (true)
                        {
                            if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1))
                                return true;
                            j++;
                            if (match.Length == j)
                                return false;
                        }
                    }

                    while (true)
                    {
                        if (pseudoRegex(pattern, i, match, j))
                            return true;
                        j++;
                        if (match.Length == j)
                            return false;
                    }
                }

                if (match.Length == j)
                    return false;

                if ((pattern[i] != '?') && (pattern[i] != match[j]))
                    return false;

                i++;
                j++;
            }
        }

        private System.String[] recommendHostkeyAlgorithms(System.String hostname)
        {
            System.String preferredAlgo = null;

            System.Collections.ArrayList keys = getAllKeys(hostname);

            for (int i = 0; i < keys.Count; i++)
            {
                System.String thisAlgo = null;

                if (keys[i] is RSAPublicKey)
                    thisAlgo = "ssh-rsa";
                else if (keys[i] is DSAPublicKey)
                    thisAlgo = "ssh-dss";
                else
                    continue;

                if (preferredAlgo != null)
                {
                    /* If we find different key types, then return null */

                    if (String.CompareOrdinal(preferredAlgo, thisAlgo) != 0)
                        return null;

                    /* OK, we found the same algo again, optimize */

                    continue;
                }
            }

            /* If we did not find anything that we know of, return null */

            if (preferredAlgo == null)
                return null;

            /* Now put the preferred algo to the start of the array.
            * You may ask yourself why we do it that way - basically, we could just
            * return only the preferred algorithm: since we have a saved key of that
            * type (sent earlier from the remote host), then that should work out.
            * However, imagine that the server is (for whatever reasons) not offering
            * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and
            * now "ssh-dss" is being used). If we then do not let the server send us
            * a fresh key of the new type, then we shoot ourself into the foot:
            * the connection cannot be established and hence the user cannot decide
            * if he/she wants to accept the new key.
            */

            if (preferredAlgo.Equals("ssh-rsa"))
                return new System.String[] { "ssh-rsa", "ssh-dss" };

            return new System.String[] { "ssh-dss", "ssh-rsa" };
        }

        /// <summary> Checks the internal hostkey database for the given hostkey.
        /// If no matching key can be found, then the hostname is resolved to an IP address
        /// and the search is repeated using that IP address.
        /// 
        /// </summary>
        /// <param name="hostname">the server's hostname, will be matched with all hostname patterns
        /// </param>
        /// <param name="serverHostKeyAlgorithm">type of hostkey, either <code>ssh-rsa</code> or <code>ssh-dss</code>
        /// </param>
        /// <param name="serverHostKey">the key blob
        /// </param>
        /// <returns> <ul>
        /// <li><code>HOSTKEY_IS_OK</code>: the given hostkey matches an entry for the given hostname</li>
        /// <li><code>HOSTKEY_IS_NEW</code>: no entries found for this hostname and this type of hostkey</li>
        /// <li><code>HOSTKEY_HAS_CHANGED</code>: hostname is known, but with another key of the same type
        /// (man-in-the-middle attack?)</li>
        /// </ul>
        /// </returns>
        /// <throws>  IOException if the supplied key blob cannot be parsed or does not match the given hostkey type. </throws>
        public virtual int verifyHostkey(System.String hostname, System.String serverHostKeyAlgorithm, byte[] serverHostKey)
        {
            System.Object remoteKey = null;

            if ("ssh-rsa".Equals(serverHostKeyAlgorithm))
            {
                remoteKey = RSASHA1Verify.decodeSSHRSAPublicKey(serverHostKey);
            }
            else if ("ssh-dss".Equals(serverHostKeyAlgorithm))
            {
                remoteKey = DSASHA1Verify.decodeSSHDSAPublicKey(serverHostKey);
            }
            else
                throw new System.ArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm);

            int result = checkKey(hostname, remoteKey);

            if (result == HOSTKEY_IS_OK)
                return result;

            System.Net.IPAddress[] ipAdresses = null;

            try
            {
                //UPGRADE_TODO: The equivalent in .NET for method 'java.net.InetAddress.getAllByName' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                ipAdresses = System.Net.Dns.GetHostByName(hostname).AddressList;
            }
            catch (System.Exception e)
            {
                return result;
            }

            for (int i = 0; i < ipAdresses.Length; i++)
            {
                int newresult = checkKey(ipAdresses[i].ToString(), remoteKey);

                if (newresult == HOSTKEY_IS_OK)
                    return newresult;

                if (newresult == HOSTKEY_HAS_CHANGED)
                    result = HOSTKEY_HAS_CHANGED;
            }

            return result;
        }

        /// <summary> Adds a single public key entry to the a known_hosts file.
        /// This method is designed to be used in a {@link ServerHostKeyVerifier}.
        /// 
        /// </summary>
        /// <param name="knownHosts">the file where the publickey entry will be appended.
        /// </param>
        /// <param name="hostnames">a list of hostname patterns - at least one most be specified. Check out the
        /// OpenSSH sshd man page for a description of the pattern matching algorithm.
        /// </param>
        /// <param name="serverHostKeyAlgorithm">as passed to the {@link ServerHostKeyVerifier}.
        /// </param>
        /// <param name="serverHostKey">as passed to the {@link ServerHostKeyVerifier}.
        /// </param>
        /// <throws>  IOException </throws>
        public static void addHostkeyToFile(System.IO.FileInfo knownHosts, System.String[] hostnames, System.String serverHostKeyAlgorithm, byte[] serverHostKey)
        {
            if ((hostnames == null) || (hostnames.Length == 0))
                throw new System.ArgumentException("Need at least one hostname specification");

            if ((serverHostKeyAlgorithm == null) || (serverHostKey == null))
                throw new System.ArgumentException();

            System.IO.StringWriter writer = new System.IO.StringWriter();

            for (int i = 0; i < hostnames.Length; i++)
            {
                if (i != 0)
                    writer.Write(',');
                writer.Write(hostnames[i]);
            }

            writer.Write(' ');
            writer.Write(serverHostKeyAlgorithm);
            writer.Write(' ');
            writer.Write(Base64.encode(serverHostKey));
            writer.Write("\n");

            char[] entry = writer.ToString().ToCharArray();

            System.IO.FileStream raf = new System.IO.FileStream(knownHosts.FullName, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite);

            long len = raf.Length;

            if (len > 0)
            {
                raf.Seek(len - 1, System.IO.SeekOrigin.Begin);
                int last = raf.ReadByte();
                if (last != '\n')
                    raf.WriteByte((System.Byte)'\n');
            }
            byte[] byteArray = StringEncoder.GetBytes(new System.String(entry));
            raf.Write(byteArray, 0, byteArray.Length);
            raf.Close();
        }

        /// <summary> Generates a "raw" fingerprint of a hostkey.
        /// 
        /// </summary>
        /// <param name="type">either "md5" or "sha1"
        /// </param>
        /// <param name="keyType">either "ssh-rsa" or "ssh-dss"
        /// </param>
        /// <param name="hostkey">the hostkey
        /// </param>
        /// <returns> the raw fingerprint
        /// </returns>
        static private byte[] rawFingerPrint(System.String type, System.String keyType, byte[] hostkey)
        {
            Digest dig = null;

            if ("md5".Equals(type))
            {
                dig = new MD5();
            }
            else if ("sha1".Equals(type))
            {
                dig = new SHA1();
            }
            else
                throw new System.ArgumentException("Unknown hash type " + type);

            if ("ssh-rsa".Equals(keyType))
            {
            }
            else if ("ssh-dss".Equals(keyType))
            {
            }
            else
                throw new System.ArgumentException("Unknown key type " + keyType);

            if (hostkey == null)
                throw new System.ArgumentException("hostkey is null");

            dig.update(hostkey);
            byte[] res = new byte[dig.DigestLength];
            dig.digest(res);
            return res;
        }

        /// <summary> Convert a raw fingerprint to hex representation (XX:YY:ZZ...).</summary>
        /// <param name="fingerprint">raw fingerprint
        /// </param>
        /// <returns> the hex representation
        /// </returns>
        static private System.String rawToHexFingerprint(byte[] fingerprint)
        {
            //UPGRADE_NOTE: Final was removed from the declaration of 'alpha '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
            char[] alpha = "0123456789abcdef".ToCharArray();

            System.Text.StringBuilder sb = new System.Text.StringBuilder();

            for (int i = 0; i < fingerprint.Length; i++)
            {
                if (i != 0)
                    sb.Append(':');
                int b = fingerprint[i] & 0xff;
                sb.Append(alpha[b >> 4]);
                sb.Append(alpha[b & 15]);
            }

            return sb.ToString();
        }

        /// <summary> Convert a raw fingerprint to bubblebabble representation.</summary>
        /// <param name="raw">raw fingerprint
        /// </param>
        /// <returns> the bubblebabble representation
        /// </returns>
        static private System.String rawToBubblebabbleFingerprint(byte[] raw)
        {
            //UPGRADE_NOTE: Final was removed from the declaration of 'v '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
            char[] v = "aeiouy".ToCharArray();
            //UPGRADE_NOTE: Final was removed from the declaration of 'c '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
            char[] c = "bcdfghklmnprstvzx".ToCharArray();

            System.Text.StringBuilder sb = new System.Text.StringBuilder();

            int seed = 1;

            int rounds = (raw.Length / 2) + 1;

            sb.Append('x');

            for (int i = 0; i < rounds; i++)
            {
                if (((i + 1) < rounds) || ((raw.Length) % 2 != 0))
                {
                    sb.Append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]);
                    sb.Append(c[(raw[2 * i] >> 2) & 15]);
                    sb.Append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]);

                    if ((i + 1) < rounds)
                    {
                        sb.Append(c[(((raw[(2 * i) + 1])) >> 4) & 15]);
                        sb.Append('-');
                        sb.Append(c[(((raw[(2 * i) + 1]))) & 15]);
                        // As long as seed >= 0, seed will be >= 0 afterwards
                        seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36;
                    }
                }
                else
                {
                    sb.Append(v[seed % 6]); // seed >= 0, therefore index positive
                    sb.Append('x');
                    sb.Append(v[seed / 6]);
                }
            }

            sb.Append('x');

            return sb.ToString();
        }

        /// <summary> Convert a ssh2 key-blob into a human readable hex fingerprint.
        /// Generated fingerprints are identical to those generated by OpenSSH.
        /// <p>
        /// Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47.
        /// </summary>
        /// <param name="keytype">either "ssh-rsa" or "ssh-dss"
        /// </param>
        /// <param name="publickey">key blob
        /// </param>
        /// <returns> Hex fingerprint
        /// </returns>
        public static System.String createHexFingerprint(System.String keytype, byte[] publickey)
        {
            byte[] raw = rawFingerPrint("md5", keytype, publickey);
            return rawToHexFingerprint(raw);
        }

        /// <summary> Convert a ssh2 key-blob into a human readable bubblebabble fingerprint.
        /// The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints
        /// that are easier to remember for humans.
        /// <p>
        /// Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux.
        /// 
        /// </summary>
        /// <param name="keytype">either "ssh-rsa" or "ssh-dss"
        /// </param>
        /// <param name="publickey">key data
        /// </param>
        /// <returns> Bubblebabble fingerprint
        /// </returns>
        public static System.String createBubblebabbleFingerprint(System.String keytype, byte[] publickey)
        {
            byte[] raw = rawFingerPrint("sha1", keytype, publickey);
            return rawToBubblebabbleFingerprint(raw);
        }
    }
}